import * as cc from 'cc';
const { ccclass, property } = cc._decorator;

export class AudioSetting {
    constructor(public volume: number = 1, public progress: number = 0) {}
}

@ccclass('AudioManager')
export class AudioManager extends cc.Component {
    private static instance: AudioManager = null!;

    public static get Instance() {
        return AudioManager.instance;
    }

    protected onLoad(): void {
        AudioManager.instance = this;
    }

    // clip -> AudioSource 实例（单实例，不允许同一个 clip 同时播放多个实例）
    private clipMap = new Map<cc.AudioClip, cc.AudioSource>();

    // clip 未播放时的起始偏移（秒）
    private clipStartOffset = new Map<cc.AudioClip, number>();

    // clip -> scheduled callback 函数（用于取消）
    private clipEndCallback = new Map<cc.AudioClip, () => void>();

    // clip 的默认起始偏移与音量通过 clipStartOffset 与 clipVolume 管理
    private clipVolume = new Map<cc.AudioClip, number>();
    // 全局总音量（0..1），所有播放音量 = masterVolume * clipVolume
    private masterVolume: number = 1;
    /**
     *
     * @param url 音频地址
     * @param type 音频类型
     * @param callback 音频回调
     */
    /**
     * 播放一次（若该 clip 已在播放，则直接忽略）
     * callback 在播放结束后调用（如果 clip 有 duration）
     */
    play(clip: cc.AudioClip, callback?: VoidFunction) {
        if (!clip) return;

        const existing = this.clipMap.get(clip);
        if (
            existing &&
            (existing.playing || (existing as any).isPlaying)
        ) {
            // 已在播放，不允许同时播放同一 clip
            return;
        }

        const source = this.getOrCreateSource(clip);
        // 取消循环
        source.loop = false;

    // 应用 clip 的音量（若有设置）乘以全局总音量
    source.volume = this.masterVolume * (this.clipVolume.get(clip) ?? 1);

        // 如果之前设置了起始偏移，则设置 currentTime
        const offset = this.clipStartOffset.get(clip) || 0;
        try {
            source.currentTime = offset;
        } catch (e) {
            // 某些平台可能不支持直接设置 currentTime
        }

        source.play();

        // 安排结束回调
        if (callback) {
            this.scheduleEndCallback(clip, clip, callback, source);
        }
    }

    /**
     * 循环播放
     */
    playLoop(clip: cc.AudioClip) {
        if (!clip) return;
        const existing = this.clipMap.get(clip);
        if (
            existing &&
            (existing.playing || (existing as any).isPlaying)
        ) {
            return;
        }
        const source = this.getOrCreateSource(clip);
        source.loop = true;
    source.volume = this.masterVolume * (this.clipVolume.get(clip) ?? 1);
        const offset = this.clipStartOffset.get(clip) || 0;
        try {
            (source as any).currentTime = offset;
        } catch (e) {}
        source.play();
    }

    pause(clip: cc.AudioClip) {
        const source = this.clipMap.get(clip);
        if (!source) return;
        try {
            source.pause();
        } catch (e) {
            // ignore
        }
    }

    resume(clip: cc.AudioClip) {
        const source = this.clipMap.get(clip);
        if (!source) return;
        try {
            source.play();
        } catch (e) {}
    }

    stop(clip: cc.AudioClip) {
        const source = this.clipMap.get(clip);
        if (!source) return;
        try {
            source.stop();
        } catch (e) {}
        // 取消可能的结束回调
        const cb = this.clipEndCallback.get(clip);
        if (cb) {
            this.unschedule(cb);
            this.clipEndCallback.delete(clip);
        }
        // 重置起始偏移为 0
        this.clipStartOffset.set(clip, 0);
    }

    /**
     * 设置播放进度（秒）。如果未播放则记录为下一次播放的起始点；若在播放则立即生效。
     */
    setProgress(clip: cc.AudioClip, value: number) {
        if (!clip) return;
        const source = this.clipMap.get(clip);
        if (source && source.playing) {
            try {
                source.currentTime = value;
            } catch (e) {}
        } else {
            this.clipStartOffset.set(clip, value);
        }
    }

    getProgress(clip: cc.AudioClip) {
        const source = this.clipMap.get(clip);
        if (source) {
            try {
                return source.currentTime || 0;
            } catch (e) {
                return this.clipStartOffset.get(clip) || 0;
            }
        }
        return this.clipStartOffset.get(clip) || 0;
    }
    /**
     *  设置全部音频类型音量
     */
    /**
     * 按 clip 设置音量（0..1）
     */
    setVolume(clip: cc.AudioClip, value: number) {
        value = Math.max(0, Math.min(1, value));
        this.clipVolume.set(clip, value);
        const source = this.clipMap.get(clip);
        if (source) source.volume = this.masterVolume * value;
    }

    /**
     *  获取音频类型音量
     */
    getVolume(clip: cc.AudioClip) {
        return this.clipVolume.get(clip) ?? 1;
    }

    /**
     * 设置全局总音量（0..1），会即时应用到所有正在播放的音源
     */
    setMainVolume(value: number) {
        this.masterVolume = Math.max(0, Math.min(1, value));
        // 立即应用到所有正在存在的 AudioSource
        this.clipMap.forEach((source, clip) => {
            const v = this.clipVolume.get(clip) ?? 1;
            source.volume = this.masterVolume * v;
        });
    }

    getMainVolume() {
        return this.masterVolume;
    }

    private getOrCreateSource(clip: cc.AudioClip): cc.AudioSource {
        let source = this.clipMap.get(clip);
        if (source) return source;

        const node = new cc.Node(`audio-${clip.name || 'clip'}`);
        node.parent = this.node;

        source = node.addComponent(cc.AudioSource);
        source.clip = clip;
        this.clipMap.set(clip, source);
        return source;
    }

    // removed type related helper

    private scheduleEndCallback(
        clipKey: cc.AudioClip,
        clip: any,
        callback: VoidFunction,
        source: cc.AudioSource,
    ) {
        // 如果 clip 有 duration，则 scheduleOnce 回调
        const duration = clip.duration || 0;
        let offset = 0;
        try {
            offset = source.currentTime || 0;
        } catch (e) {
            offset = this.clipStartOffset.get(clipKey) || 0;
        }
        const remaining = Math.max(0, duration - offset);
        if (duration > 0) {
            const wrapper = () => {
                // 回调后清理
                try {
                    callback();
                } catch (e) {}
                this.clipEndCallback.delete(clipKey);
            };
            this.clipEndCallback.set(clipKey, wrapper);
            this.scheduleOnce(wrapper, remaining);
        } else {
            // 无 duration,不做处理
        }
    }
}
